Skip to content

docs: purge never-compiled doc examples — compile, delete, or justify (#146)#147

Merged
lxsaah merged 18 commits into
mainfrom
docs/146-doctest-conversion
Jun 13, 2026
Merged

docs: purge never-compiled doc examples — compile, delete, or justify (#146)#147
lxsaah merged 18 commits into
mainfrom
docs/146-doctest-conversion

Conversation

@lxsaah

@lxsaah lxsaah commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #146 with the revised policy (maintainer decision mid-work): instead of systematically converting every rust,ignore example, remove them unless there is a good reason to keep one — and where the example is the crate's primary usage documentation and the dependencies allow it, convert it to a compiled doctest instead. Stacked on #145 (retarget down the stack as it merges).

Result: 108 ignored fences → 24, every survivor carrying an explicit "Illustrative (not compiled: …)" reason inline. No API or behavior changes; Cargo.toml changes are dev-deps only.

Converted to compiled doctests (~30 examples, now CI-checked)

  • Crate-level quick starts: aimdb-sync (incl. the "no #[tokio::main]" flagship), uds/websocket/mqtt/knx connectors, persistence + sqlite backend.
  • Core: producer/consumer module examples (typed_api), TopicProvider, extensions storage, AimxConfig::socket_permissions, the RecordKey Hash/Borrow contract snippet, connector module example.
  • Trait-extension docs: MQTT with_qos/with_retain link ext, websocket AuthHandler (bearer-token example), websocket server/client builder chains, data-contracts log_tap + MigrationStep.

Rot the conversion surfaced and fixed

Key-less configure()/producer()/consumer() calls (the API takes a key), builder chains that cannot compile (register_record returns &mut Self, build() takes self), .with_buffer()/.finish() on receivers that don't have them, a deserializer returning Box<T> instead of T, AimDbSyncExt's example calling the async build() synchronously, DbError::RecordNotFound vs RecordKeyNotFound, and source + link_from on one record (a build-time conflict) in the MQTT quick start.

Deleted (~50 fragments)

Method-level examples that restated the signature: builder key-access helpers (produce/subscribe/producer/consumer/resolve_key/…), registrar setters (source/tap/link_to/with_*), connector constructors, internal SPI sketches (RouterBuilder, JsonBufferReader, json access), and duplicates of crate-level examples.

Kept as ignore (24, each with a written reason)

  • Core examples wiring downstream crates it cannot depend on (adapters, connectors, the .buffer() registrar ext trait) — e.g. with_connector's three-variant example, remote module usage, sans-io connector-author sketches.
  • aimdb-derive: expansion targets aimdb_core::RecordKey (circular dev-dependency); compiled integration tests live in aimdb-core.
  • Embedded/wasm-only: Embassy peripherals/network stack, #[wasm_bindgen] factory.
  • Macro grammar sketches with placeholder types (migration_chain!), pointing at compiled real usage.

The three buffer_sized recipe mini-fences in the Embassy adapter were folded into prose bullets — recipes preserved, no uncompiled code.

Validation

  • make fmt-checkmake clippy (all feature combos, -D warnings) ✅ make test ✅ (186 doctests passing suite-wide)
  • Per-crate cargo test --doc run for every touched crate during conversion.

🤖 Generated with Claude Code

lxsaah and others added 16 commits June 11, 2026 21:12
…6 W1 inbound)

Replace the per-message Box<dyn Any> inbound path (DeserializerKind ->
produce_any downcast) with a fused IngestFn built in
InboundConnectorBuilder::finish() where T is known: deserialize + produce
in one typed closure, no erasure crossing and no boxed future per message
(Producer::produce is sync + infallible, design 029).

- IngestFn / IngestFactoryFn replace DeserializerFn/ContextDeserializerFn/
  DeserializerKind and ProducerTrait/ProducerFactoryFn (all deleted)
- InboundConnectorLink carries the ingest factory (non-optional; finish()
  validates the deserializer before registering, unchanged error strings)
- Router::route is now a sync fn taking &RuntimeContext (every production
  caller already passed Some(&ctx); the context-skip branch is
  unrepresentable with fused closures, its test removed)
- collect_inbound_routes returns Vec<(String, IngestFn)>
- pump_source / pump_client inbound / ws dispatch drop one .await
- delete dead TypedRecord::create_producer_trait

Registrar API (with_deserializer/_raw, link_from) is source-compatible;
MQTT/KNX/WS builders pass routes opaquely and compile unchanged.

Part of design 036 W1 (data-plane de-Any).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion (036 W1 outbound)

Replace the per-message Box<dyn Any> outbound path (subscribe_any ->
recv_any -> SerializerFn(&dyn Any) downcast, plus topic_any(&dyn Any))
with a fused SerializedSource built in OutboundConnectorBuilder::finish()
where T is known: its readers yield destination + serialized payload
directly (subscribe -> recv -> resolve topic -> serialize, all typed).

- SerializedSource / SerializedReader / SerializedValue / SourceFactoryFn
  replace ConsumerTrait/AnyReader/SerializerKind/SerializerFn/
  ContextSerializerFn/ConsumerFactoryFn and the erased TopicProviderAny/
  TopicProviderWrapper/TopicProviderFn (all deleted; the typed
  TopicProvider<T> trait is now stored as-is)
- OutboundRoute is { topic, source, config }; ConnectorLink carries the
  source factory (non-optional; finish() validates the serializer before
  registering, unchanged error strings; the "skip links without
  serializer" branch in collect_outbound_routes is gone)
- RuntimeContext is threaded into SerializedReader::recv per call (026
  context serializers), not captured; raw serializers skip the ctx clone
- Buffer errors propagate unchanged (BufferLagged => pump continues,
  other => pump stops); serialize failures are logged and skipped inside
  the reader, observably identical to the old pump-side continue
- pump_sink / pump_client outbound collapse to recv + publish

What disappears per message: the Box<dyn Any> allocation, two downcasts,
the topic_any erasure crossing, and the subscribe_any boxed future. The
one remaining Box::pin per recv is the object-safe-async cost that
already existed.

Registrar API (with_serializer/_raw, with_topic_provider, link_to) is
source-compatible; the KNX fake-gateway and session smoke tests pass
unmodified.

Part of design 036 W1 (data-plane de-Any).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…asure (036 W1 wrap-up)

- delete SerializeError::TypeMismatch — both constructors died with the
  W1 fusion (the downcasts are gone); DbError::TypeMismatch is unrelated
  and stays
- delete dead ConnectorClient (held Arc<dyn Any>, zero users) and
  OutboundConnectorLink (zero users)
- document on JoinTrigger why the join fan-in deliberately keeps its
  Box<dyn Any + Send> (the erasure is the multi-type join API)
- CHANGELOG entries (global + aimdb-core + websocket-connector)
- check in design docs 034/035/036 and amend the 036 W1 acceptance grep:
  DynBuffer::as_any and the session auth ext slots are setup-time hits
  the original literal grep missed

Part of design 036 W1 (data-plane de-Any).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AnyRecord carried ~22 methods spanning four concerns; every remote-access
or profiling change churned the core storage contract. Split by consumer:

- AnyRecord (6 methods): storage/lifecycle only — validate, as_any(_mut),
  drain_config_errors, set_writable_erased, plus the cfg-gated
  json_access() accessor so the remote-access gate lives in one place.
- RecordIntrospect (supertrait): graph/metadata introspection consumed by
  the builder's dependency-graph construction, route collection, and
  list_records.
- JsonRecordAccess (cfg remote-access): latest_json / subscribe_json /
  set_from_json, reached via json_access(); the runtime
  .with_remote_access() checks stay inside the methods, so behavior is
  unchanged. Gate is `remote-access` (the 036 sketch said json-serialize).
- RecordMetricsReset (supertrait): the cfg-gated no-op-default resets.

Supertrait wiring keeps every dyn AnyRecord call site compiling unchanged;
only the four JSON call sites switch to the accessor. Registry storage
stays Vec<Box<dyn AnyRecord>>. Drops the dead outbound_connector_urls
(cfg std) — zero callers in-tree and in aimdb-pro.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…036 W5)

intern() leaked a fresh allocation on every call, so re-interning the same
key leaked again. A global dedup table (std Mutex / spin Mutex, same
pattern as the TypedRecord field locks) now returns the existing 'static
allocation for a known key, bounding the leak by the number of *distinct*
dynamic keys — the actual lifetime contract of a record key. The contract
is documented loudly on intern(), and the debug-build tripwire now counts
distinct keys instead of calls.

The &'static str / Copy design stays; an Arc<str> key variant remains
rejected (forks RecordKey into two shapes — the 034 §3.1 mistake).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion (036 W6)

The 18-line no-op #[defmt::global_logger] + panic handler + host time
driver block existed in three copies (embassy-adapter session_smoke,
embassy-adapter buffer.rs tests, serial-connector embassy_smoke). Per 035
§2.4, an exported host_test_stubs! macro in aimdb-embassy-adapter now
holds the single definition; each test binary expands it once, preserving
the once-per-binary requirement of #[global_logger]/time_driver_impl!.

The time driver uses the wake_by_ref variant (buffer.rs's superset
behavior: zero-duration sleeps complete; the smokes never sleep). The
serial-connector defmt/embassy-time-driver dev-deps stay — the expansion
references them at the invocation site; the Cargo.toml comment now says
so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, W7 skipped

- W3: Nucleo firmware build verified (thumbv8m.main-none-eabihf); single
  run on a main build is the bar now that #140 is merged. Bench session
  is the remaining work and the gate for closing 035.
- W4: deferred pending W3 scenario-1 AckTimeout evidence (decision
  2026-06-12). Design pre-decided for the trigger: buffer the sent frame
  (option a) — GroupWrite already carries a 254-byte APDU buffer, so the
  semantic-content variant saves ~350 B total, no real RAM argument.
- W7: skipped entirely (decision 2026-06-12); no audit will be run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d miss (036 W4)

KNXnet/IP 3.8.4 says: repeat an unACKed TUNNELING_REQUEST once after the
timeout, then tear the connection down on the second miss. The engine
previously expired and warned only — hardware bench evidence (W3,
2026-06-12) showed ten button-press writes silently lost during a link
outage's heartbeat-detection window (~65 s).

TunnelConfig gains ack_retransmits (default 1 = spec behavior; 0 = the
old expire-and-warn for the previous semantics). The pending-ACK slot
buffers the sent frame (option (a) per the 036 W4 decision record:
GroupWrite already carries a 254-byte APDU, so rebuilding from semantic
content saves ~350 B total — not worth the rebuild path). On expiry with
retries left the identical frame is re-sent (same sequence counter) and
the timer re-armed; on final expiry AckTimeout is reported and the
engine disconnects so queued commands flush after the re-handshake
instead of vanishing into a dead tunnel. Eviction on map overflow stays
warn-only (overflow is not confirmed loss).

Behavior change at default config: a tunnel with a persistently
unanswered write now reconnects within ~2× ack_timeout_ms instead of
staying connected. Retransmit delay is ack_timeout_ms (3 s, the
pre-engine constant); strict spec timing is one config knob away
(ack_timeout_ms = 1_000).

Tests: engine units (identical-frame retransmit, ack-after-retransmit,
disconnect on second miss, legacy mode pinned at ack_retransmits=0) +
fake-gateway test dropping the first ACK and asserting the byte-identical
repeat and tunnel survival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… W3 scope

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Doc-rot fixes from the post-036 debt scan; no wire or API behavior
changes (the one wire-visible byte change — the client hello version —
is corrected from a stale "1.0" to the "2.0" the server already
announces; the server never validates it).

- remote::PROTOCOL_VERSION corrected to "2.0", documented, and exported;
  the AimX dispatch Welcome and aimdb-client both use it now, so client
  and server can no longer drift. The dead, never-exported v1 untagged
  Message envelope and its helpers are deleted.
- remote module docs: "AimX v1" + link to the nonexistent
  docs/design/remote-access/aimx-v1.md replaced with the v2 NDJSON
  tagged-frame description pointing at session::aimx and
  remote-access-via-connectors.md; stale .build()? example fixed to the
  (db, runner) = build().await? shape.
- connector module docs: removed-`.link()` API replaced with the real
  configure/link_to pattern (the old example used a RecordConfig::builder
  API that never existed); ConnectorUrl no longer advertises Kafka/HTTP
  connector semantics for connectors that don't exist — documented as
  scheme-agnostic with real schemes (mqtt, knx, ws, uds, serial).
- builder.rs AimDb example fixed: register_record returns &mut Self, so
  the old chained .build() could not compile.
- aimdb-client README rewritten to match reality (AimxConnection, v2
  wire, endpoint URLs incl. serial); crate doc + aimdb-cli doc updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#146)

Resolves the ignored-doctest debt from the post-036 scan with the revised
policy (maintainer decision on #146): remove `rust,ignore` examples unless
there is a good reason to keep one. Result: 108 ignored fences → 24, and
every survivor carries an explicit "Illustrative (not compiled: …)" reason.

- Converted to compiled doctests (`no_run`/`rust`) where the crate's deps
  allow it: the crate-level quick starts of aimdb-sync, aimdb-uds-connector,
  aimdb-websocket-connector, aimdb-mqtt-connector, aimdb-knx-connector,
  aimdb-persistence(+sqlite); core's producer/consumer module examples,
  TopicProvider, extensions, AimxConfig, RecordKey Hash-contract; the
  websocket AuthHandler and MQTT link-ext trait docs. ~30 examples now
  compile under `make test` (186 doctests passing suite-wide).
- Conversion surfaced real rot, now fixed in the surviving examples:
  key-less `configure()`/`producer()`/`consumer()` calls (API takes a key),
  builder chains that can't compile (`&mut Self` → by-value `build()`),
  `with_buffer`/`finish()` on the wrong receiver, a deserializer returning
  `Box<T>` instead of `T`, sync's `AimDbSyncExt` example calling the async
  `build()` synchronously, and `DbError::RecordNotFound` vs
  `RecordKeyNotFound`.
- Deleted ~50 method-level fragments that restated the signature (builder
  key-access helpers, registrar setters, connector constructors, internal
  SPI sketches) plus duplicates of the crate-level examples.
- Kept 24 as `ignore`, each with a written reason: downstream-crate types
  core cannot depend on (adapter/connector wiring, `.buffer()` ext trait),
  proc-macro expansion targeting aimdb-core (circular dev-dependency),
  embedded/wasm-only code (Embassy peripherals, `#[wasm_bindgen]`),
  and macro grammar sketches with placeholder types.
- Dev-deps: aimdb-uds-connector gains serde/serde_json for its quick start;
  aimdb-sync's example uses its existing aimdb-tokio-adapter dependency.

Closes #146.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from docs/036-doc-rot-fixes to main June 13, 2026 19:17
@lxsaah lxsaah merged commit 3d64818 into main Jun 13, 2026
6 checks passed
@lxsaah lxsaah deleted the docs/146-doctest-conversion branch June 13, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert rust,ignore doc examples to compile-checked forms (no_run)

1 participant